/*
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 * Copyright (C) 2017 Furrtek
 * Copyright (C) 2023 TJ Baginski
 * Copyright (C) 2025 Tommaso Ventafridda
 *
 * This file is part of PortaPack.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#include "ble_rx_app.hpp"
#include "ui_modemsetup.hpp"

#include "modems.hpp"
#include "audio.hpp"
#include "io_file.hpp"
#include "rtc_time.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_fileman.hpp"
#include "ui_textentry.hpp"
#include "usb_serial_asyncmsg.hpp"

#include <string_view>

using namespace portapack;
using namespace modems;
namespace fs = std::filesystem;

#define BLE_RX_NO_ERROR 0
#define BLE_RX_LIST_FILENAME_EMPTY_ERROR 1
#define BLE_RX_ENTRY_FILENAME_EMPTY_ERROR 2
#define BLE_RX_LIST_SAVE_ERROR 3
#define BLE_RX_ENTRY_SAVE_ERROR 4

static uint8_t ble_rx_error = BLE_RX_NO_ERROR;

void BLELogger::log_raw_data(const std::string& data) {
    log_file.write_entry(data);
}

std::string pad_string_with_spaces(int snakes) {
    std::string paddedStr(snakes, ' ');
    return paddedStr;
}

uint64_t copy_mac_address_to_uint64(const uint8_t* macAddress) {
    uint64_t result = 0;

    // Copy each byte of the MAC address to the corresponding byte in the uint64_t.
    for (int i = 0; i < 6; ++i) {
        result |= static_cast<uint64_t>(macAddress[i]) << ((5 - i) * 8);
    }

    return result;
}

MAC_VENDOR_STATUS lookup_mac_vendor_status(const uint8_t* mac_address, std::string& vendor_name) {
    static bool db_checked = false;
    static bool db_exists = false;

    if (!db_checked) {
        database db;
        database::MacAddressDBRecord dummy_record;
        int test_result = db.retrieve_macaddress_record(&dummy_record, "000000");
        db_exists = (test_result != DATABASE_NOT_FOUND);
        db_checked = true;
    }

    if (!db_exists) {
        vendor_name = "macaddress.db not found";
        return MAC_DB_NOT_FOUND;
    }

    database db;
    database::MacAddressDBRecord record;

    // Convert MAC address to hex string
    std::string mac_hex = "";
    for (int i = 0; i < 3; i++) {
        // Only need first 3 bytes for OUI
        mac_hex += to_string_hex(mac_address[i], 2);
    }

    int result = db.retrieve_macaddress_record(&record, mac_hex);

    if (result == DATABASE_RECORD_FOUND) {
        vendor_name = std::string(record.vendor_name);
        return MAC_VENDOR_FOUND;
    } else {
        vendor_name = "Unknown";
        return MAC_VENDOR_NOT_FOUND;
    }
}

std::string lookup_mac_vendor(const uint8_t* mac_address) {
    std::string vendor_name;
    lookup_mac_vendor_status(mac_address, vendor_name);
    return vendor_name;
}

void reverse_byte_array(uint8_t* arr, int length) {
    int start = 0;
    int end = length - 1;

    while (start < end) {
        // Swap elements at start and end
        uint8_t temp = arr[start];
        arr[start] = arr[end];
        arr[end] = temp;

        // Move the indices towards the center
        start++;
        end--;
    }
}

namespace ui {

std::string pdu_type_to_string(ADV_PDU_TYPE type) {
    std::string pduTypeStr = "";

    switch (type) {
        case ADV_IND:
            pduTypeStr += "ADV_IND";
            break;
        case ADV_DIRECT_IND:
            pduTypeStr += "ADV_DIRECT_IND";
            break;
        case ADV_NONCONN_IND:
            pduTypeStr += "ADV_NONCONN_IND";
            break;
        case SCAN_REQ:
            pduTypeStr += "SCAN_REQ";
            break;
        case SCAN_RSP:
            pduTypeStr += "SCAN_RSP";
            break;
        case CONNECT_REQ:
            pduTypeStr += "CONNECT_REQ";
            break;
        case ADV_SCAN_IND:
            pduTypeStr += "ADV_SCAN_IND";
            break;
        case RESERVED0:
        case RESERVED1:
        case RESERVED2:
        case RESERVED3:
        case RESERVED4:
        case RESERVED5:
        case RESERVED6:
        case RESERVED7:
        case RESERVED8:
            pduTypeStr += "RESERVED";
            break;
        default:
            pduTypeStr += "UNKNOWN";
            break;
    }

    return pduTypeStr;
}

template <>
void RecentEntriesTable<BleRecentEntries>::draw(
    const Entry& entry,
    const Rect& target_rect,
    Painter& painter,
    const Style& style,
    RecentEntriesColumns& columns) {
    std::string line{};
    line.reserve(40);
    if (!entry.nameString.empty() && entry.include_name) {
        line = entry.nameString;
    } else {
        line = to_string_mac_address(entry.packetData.macAddress, 6, false);
    }
    line.resize(columns.at(0).second, ' ');

    std::string hitsStr;

    if (!entry.informationString.empty()) {
        hitsStr = entry.informationString;
    } else {
        hitsStr = to_string_dec_int(entry.numHits);
    }

    // Pushing single digit values down right justified.
    int hitsDigits = hitsStr.length();
    uint8_t hits_spacing = 8 - hitsDigits;

    // Pushing single digit values down right justified.
    std::string dbStr = to_string_dec_int(entry.dbValue);
    int dbDigits = dbStr.length();
    uint8_t db_spacing = 5 - dbDigits;

    line += pad_string_with_spaces(hits_spacing) + hitsStr;

    line += pad_string_with_spaces(db_spacing) + dbStr;

    line.resize(target_rect.width() / 8, ' ');

    Style row_style = (entry.vendor_status == MAC_VENDOR_FOUND) ? style : Style{style.font, style.background, Color::grey()};

    painter.draw_string(target_rect.location(), row_style, line);
}

BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry)
    : nav_{nav},
      entry_{entry} {
    add_children({&button_done,
                  &button_send,
                  &button_save,
                  &label_mac_address,
                  &text_mac_address,
                  &label_pdu_type,
                  &text_pdu_type,
                  &label_vendor,
                  &text_vendor,
                  &labels});

    text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
    text_pdu_type.set(pdu_type_to_string(entry.pduType));
    text_vendor.set(entry.vendor_name.empty() ? lookup_mac_vendor(entry.packetData.macAddress) : entry.vendor_name);

    button_done.on_select = [&nav](const ui::Button&) {
        nav.pop();
    };

    button_send.on_select = [this, &nav](const ui::Button&) {
        auto packetToSend = build_packet(entry_);
        nav.set_on_pop([packetToSend, &nav]() {
            nav.replace<BLETxView>(packetToSend);
        });
        nav.pop();
    };

    button_save.on_select = [this, &nav](const ui::Button&) {
        auto packetToSave = build_packet(entry_);

        packetFileBuffer = "";
        text_prompt(
            nav,
            packetFileBuffer,
            64,
            ENTER_KEYBOARD_MODE_ALPHA,
            [this, packetToSave](std::string& buffer) {
                on_save_file(buffer, packetToSave);
            });
    };
}

void BleRecentEntryDetailView::on_save_file(const std::string value, BLETxPacket packetToSave) {
    if (value.length() > 0) {
        ensure_directory(packet_save_path);
        auto folder = packet_save_path.parent_path();
        auto ext = packet_save_path.extension();
        auto new_path = folder / value + ext;
        ble_rx_error = saveFile(new_path, packetToSave);
    } else {
        nav_.pop();
        ble_rx_error = BLE_RX_ENTRY_FILENAME_EMPTY_ERROR;
    }
}

bool BleRecentEntryDetailView::saveFile(const std::filesystem::path& path, BLETxPacket packetToSave) {
    File f;
    auto error = f.create(path);
    if (error)
        return BLE_RX_ENTRY_SAVE_ERROR;

    std::string macAddressStr = packetToSave.macAddress;
    std::string advertisementDataStr = packetToSave.advertisementData;
    std::string packetCountStr = packetToSave.packetCount;

    std::string packetString = macAddressStr + ' ' + advertisementDataStr + ' ' + packetCountStr;

    f.write(packetString.c_str(), packetString.length());

    return BLE_RX_NO_ERROR;
}

void BleRecentEntryDetailView::update_data() {
}

void BleRecentEntryDetailView::focus() {
    button_done.focus();
}

Rect BleRecentEntryDetailView::draw_field(
    Painter& painter,
    const Rect& draw_rect,
    const Style& style,
    const std::string& label,
    const std::string& value) {
    const int label_length_max = 4;

    painter.draw_string(Point{draw_rect.left(), draw_rect.top()}, style, label);
    painter.draw_string(Point{draw_rect.left() + (label_length_max + 1) * 8, draw_rect.top()}, style, value);

    return {draw_rect.left(), draw_rect.top() + draw_rect.height(), draw_rect.width(), draw_rect.height()};
}

void BleRecentEntryDetailView::paint(Painter& painter) {
    View::paint(painter);

    const auto s = style();
    const auto rect = screen_rect();

    auto field_rect = Rect{rect.left(), rect.top() + 64, rect.width(), 16};

    uint8_t type[total_data_lines];
    uint8_t length[total_data_lines];
    uint8_t data[total_data_lines][40];

    int currentByte = 0;
    int currentPacket = 0;
    int i = 0;
    int j = 0;
    int k = 0;

    switch (entry_.pduType) {
        case ADV_IND:
        case ADV_NONCONN_IND:
        case SCAN_RSP:
        case ADV_SCAN_IND: {
            ADV_PDU_PAYLOAD_TYPE_0_2_4_6* advertiseData = (ADV_PDU_PAYLOAD_TYPE_0_2_4_6*)entry_.packetData.data;

            for (currentByte = 0; (currentByte < entry_.packetData.dataLen) && (currentPacket < total_data_lines);) {
                length[currentPacket] = advertiseData->Data[currentByte++];
                type[currentPacket] = advertiseData->Data[currentByte++];

                // Subtract 1 because type is part of the length.
                for (i = 0; i < length[currentPacket] - 1; i++) {
                    data[currentPacket][i] = advertiseData->Data[currentByte++];
                }

                currentPacket++;
            }

            for (i = 0; i < currentPacket; i++) {
                uint8_t number_data_lines = ceil((float)(length[i] - 1) / 10.0);
                uint8_t current_line = 0;
                std::array<std::string, total_data_lines> data_strings{};

                for (j = 0; (j < (number_data_lines * 10)) && (j < length[i] - 1); j++) {
                    if ((j / 10) != current_line) {
                        current_line++;
                    }

                    data_strings[current_line] += to_string_hex(data[i][j], 2);
                }

                // Read the type back to the total length.
                field_rect = draw_field(painter, field_rect, s, to_string_hex(length[i]), to_string_hex(type[i]) + pad_string_with_spaces(3) + data_strings[0]);

                if (number_data_lines > 1) {
                    for (k = 1; k < number_data_lines; k++) {
                        if (!data_strings[k].empty()) {
                            field_rect = draw_field(painter, field_rect, s, "", pad_string_with_spaces(5) + data_strings[k]);
                        }
                    }
                }
            }
        } break;

        case ADV_DIRECT_IND:
        case SCAN_REQ: {
            ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)entry_.packetData.data;
            uint8_t type = 0xFF;
            field_rect = draw_field(painter, field_rect, s, to_string_hex(entry_.packetData.dataLen), to_string_hex(type) + pad_string_with_spaces(3) + to_string_mac_address(directed_mac_data->A1, 6, false));
        } break;

        case CONNECT_REQ:
        default: {
            uint8_t type = 0xFF;

            // TODO: Display Connect Request Information. For right now just printing full hex data.
            // This struct will eventually be used to break apart containing data of Connect Request.
            // ADV_PDU_PAYLOAD_TYPE_5 * connect_req = (ADV_PDU_PAYLOAD_TYPE_5 *)entry_.packetData.data;

            for (currentByte = 0; (currentByte < entry_.packetData.dataLen); currentByte++) {
                data[0][currentByte] = entry_.packetData.data[currentByte];
            }

            uint8_t number_data_lines = ceil((float)entry_.packetData.dataLen / 10.0);
            uint8_t current_line = 0;
            std::array<std::string, total_data_lines> data_strings{};

            for (j = 0; (j < (number_data_lines * 10)) && (j < entry_.packetData.dataLen); j++) {
                if ((j / 10) != current_line) {
                    current_line++;
                }

                data_strings[current_line] += to_string_hex(data[0][j], 2);
            }

            field_rect = draw_field(painter, field_rect, s, to_string_hex(entry_.packetData.dataLen), to_string_hex(type) + pad_string_with_spaces(3) + data_strings[0]);

            if (number_data_lines > 1) {
                for (k = 1; k < number_data_lines; k++) {
                    if (!data_strings[k].empty()) {
                        field_rect = draw_field(painter, field_rect, s, "", pad_string_with_spaces(5) + data_strings[k]);
                    }
                }
            }
        } break;
    }
}

void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) {
    entry_ = entry;
    text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
    text_pdu_type.set(pdu_type_to_string(entry.pduType));
    text_vendor.set(entry.vendor_name.empty() ? lookup_mac_vendor(entry.packetData.macAddress) : entry.vendor_name);
    set_dirty();
}

BLETxPacket BleRecentEntryDetailView::build_packet(BleRecentEntry entry_) {
    BLETxPacket bleTxPacket;
    memset(&bleTxPacket, 0, sizeof(BLETxPacket));

    std::string macAddressStr = to_string_mac_address(entry_.packetData.macAddress, 6, true);

    strncpy(bleTxPacket.macAddress, macAddressStr.c_str(), 12);
    strncpy(bleTxPacket.advertisementData, entry_.dataString.c_str(), entry_.packetData.dataLen * 2);
    strncpy(bleTxPacket.packetCount, "10", 3);
    bleTxPacket.packet_count = 10;

    return bleTxPacket;
}

static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
    uint64_t freq_hz;

    switch (channel_number) {
        case 37:
            freq_hz = 2'402'000'000ull;
            break;
        case 38:
            freq_hz = 2'426'000'000ull;
            break;
        case 39:
            freq_hz = 2'480'000'000ull;
            break;
        case 0 ... 10:
            freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull;
            break;
        case 11 ... 36:
            freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
            break;
        default:
            freq_hz = UINT64_MAX;
    }

    return freq_hz;
}

void BLERxView::focus() {
    options_channel.focus();
}

void BLERxView::file_error() {
    nav_.display_modal("Error", "File read error.");
}

BLERxView::BLERxView(NavigationView& nav)
    : nav_{nav} {
    baseband::run_image(portapack::spi_flash::image_tag_btle_rx);

    add_children({&rssi,
                  &channel,
                  &field_rf_amp,
                  &field_lna,
                  &field_vga,
                  &options_channel,
                  &field_frequency,
                  &label_sort,
                  &options_sort,
                  &button_filter,
                  &options_filter,
                  &check_name,
                  &check_log,
                  &check_serial_log,
                  &check_unique,
                  &check_duplicate_packets,
                  &button_find,
                  &label_found,
                  &text_found_count,
                  &button_save_list,
                  &button_clear_list,
                  &button_switch,
                  &recent_entries_view});

    async_tx_states_when_entered = portapack::async_tx_enabled;

    recent_entries_view.on_select = [this](const BleRecentEntry& entry) {
        nav_.push<BleRecentEntryDetailView>(entry);
    };

    ensure_directory(find_packet_path);
    ensure_directory(log_packets_path);
    ensure_directory(packet_save_path);

    // ------------------------------------------------------------------------------
    // Handle Check Boxes
    // ------------------------------------------------------------------------------
    logger = std::make_unique<BLELogger>();

    check_name.on_select = [this](Checkbox&, bool v) {
        name_enable = v;
        // update the include_name instance variable value of each entry in recent entries
        setAllMembersToValue(recent, &BleRecentEntry::include_name, v);
        recent_entries_view.set_dirty();
    };

    check_log.on_select = [this](Checkbox&, bool v) {
        logging = v;

        if (logger && logging)
            logger->append(bletx_dir.string() + "/Packets/BLETx_" + to_string_timestamp(rtc_time::now()) + ".TXT");
    };

    check_serial_log.on_select = [this](Checkbox&, bool v) {
        serial_logging = v;
        if (v) {
            portapack::async_tx_enabled = true;
        } else {
            portapack::async_tx_enabled = false;
        }
    };

    check_unique.on_select = [this](Checkbox&, bool v) {
        uniqueParsing = v;
        recent.clear();
        recent_entries_view.set_dirty();
    };

    check_duplicate_packets.on_select = [this](Checkbox&, bool v) {
        duplicatePackets = v;
        recent.clear();
        recent_entries_view.set_dirty();
    };

    check_name.set_value(name_enable);
    check_log.set_value(logging);
    check_serial_log.set_value(serial_logging);
    check_unique.set_value(uniqueParsing);
    check_duplicate_packets.set_value(duplicatePackets);

    // ------------------------------------------------------------------------------
    // Handle Buttons
    // ------------------------------------------------------------------------------
    filterBuffer = filter;

    button_filter.on_select = [this](Button&) {
        text_prompt(
            nav_,
            filterBuffer,
            64,
            ENTER_KEYBOARD_MODE_ALPHA,
            [this](std::string& buffer) {
                on_filter_change(buffer);
            });
    };

    button_save_list.on_select = [this, &nav](const ui::Button&) {
        listFileBuffer = "";
        text_prompt(
            nav,
            listFileBuffer,
            64,
            ENTER_KEYBOARD_MODE_ALPHA,
            [this](std::string& buffer) {
                on_save_file(buffer);
            });
    };

    button_clear_list.on_select = [this](Button&) {
        recent.clear();
        recent_entries_view.set_dirty();
    };

    button_switch.on_select = [&nav](Button&) {
        nav.replace<BLETxView>();
    };

    button_find.on_select = [this](Button&) {
        auto open_view = nav_.push<FileLoadView>(".TXT");
        open_view->on_changed = [this](std::filesystem::path new_file_path) {
            on_file_changed(new_file_path);
        };
    };

    // ------------------------------------------------------------------------------
    // Handle Options
    // ------------------------------------------------------------------------------
    field_frequency.set_step(0);

    options_channel.on_change = [this](size_t index, int32_t v) {
        channel_index = (uint8_t)index;

        // If we selected Auto don't do anything and Auto will handle changing.
        if (v == 40) {
            auto_channel = true;
            return;
        } else {
            auto_channel = false;
        }

        field_frequency.set_value(get_freq_by_channel_number(v));
        channel_number = v;

        baseband::set_btlerx(channel_number);
    };

    options_sort.on_change = [this](size_t index, int32_t v) {
        sort_index = (uint8_t)index;
        handle_entries_sort(v);
    };

    options_filter.on_change = [this](size_t index, int32_t v) {
        (void)v;
        filter_index = (uint8_t)index;
        recent.clear();
        recent_entries_view.set_dirty();
    };

    options_channel.set_selected_index(channel_index, true);
    options_sort.set_selected_index(sort_index, true);
    options_filter.set_selected_index(filter_index, true);

    // Auto-configure modem for LCR RX (will be removed later)
    baseband::set_btlerx(channel_number);

    receiver_model.enable();
}

std::string BLERxView::build_line_str(BleRecentEntry entry) {
    std::string macAddressStr = to_string_mac_address(entry.packetData.macAddress, 6, false) + ",";
    std::string timestameStr = entry.timestamp + ",";
    std::string nameStr = entry.nameString + ",";
    std::string pduStr = pdu_type_to_string(entry.pduType) + ",";
    std::string dataStr = "0x" + entry.dataString + ",";
    std::string hitsStr = to_string_dec_int(entry.numHits) + ",";
    std::string dbStr = to_string_dec_int(entry.dbValue) + ",";
    std::string channelStr = to_string_dec_int(entry.channelNumber) + ",";

    std::string lineStr = timestameStr + macAddressStr + nameStr + pduStr + dataStr + hitsStr + dbStr + channelStr;
    lineStr += pad_string_with_spaces(maxLineLength - lineStr.length());

    return lineStr;
}

void BLERxView::on_save_file(const std::string value) {
    if (value.length() > 0) {
        auto folder = packet_save_path.parent_path();
        auto ext = packet_save_path.extension();
        auto new_path = folder / value + ext;
        ble_rx_error = saveFile(new_path);
    } else {
        ble_rx_error = BLE_RX_LIST_FILENAME_EMPTY_ERROR;
    }
}

bool BLERxView::saveFile(const std::filesystem::path& path) {
    // Check to see if file was previously saved.
    bool file_existed = file_exists(path);

    // Attempt to open, if it can't be opened. Create new.
    auto src = std::make_unique<File>();
    auto error = src->open(path, false, true);

    if (error) {
        return BLE_RX_LIST_SAVE_ERROR;
    }

    for (const auto& entry : recent) {
        tempList.emplace_back(entry);
    }

    if (!file_existed) {
        src->write_line(headerStr.c_str());

        auto it = tempList.begin();

        while (it != tempList.end()) {
            BleRecentEntry entry = (BleRecentEntry)*it;
            src->write_line(build_line_str(entry).c_str());
            it++;
        }
    } else {
        // Check file for macAddressStr before adding.
        char currentLine[maxLineLength];
        uint64_t startPos = headerStr.length();
        uint64_t bytesRead = 0;
        uint64_t bytePos = 0;

        File::Size currentSize = src->size();

        auto dst = std::make_unique<File>();
        const std::filesystem::path tempFilePath = path + "~";
        auto error = dst->open(tempFilePath, false, true);

        if (error) {
            return BLE_RX_LIST_SAVE_ERROR;
        }

        dst->write_line(headerStr.c_str());

        src->seek(startPos);

        // Look for ones found and rewrite.
        do {
            memset(currentLine, 0, maxLineLength);

            bytesRead = readUntil(*src, currentLine, currentSize, '\n');

            if (!bytesRead) {
                break;
            }

            bytePos += bytesRead;

            std::string lineStr = "";
            std::string macAddressStr = "";
            BleRecentEntry foundEntry;

            char* token;
            token = strtok(currentLine, ",");

            while (token != NULL) {
                auto it = tempList.begin();

                while (it != tempList.end()) {
                    BleRecentEntry& entry = reinterpret_cast<BleRecentEntry&>(*it);

                    macAddressStr = to_string_mac_address(entry.packetData.macAddress, 6, false);

                    if (strstr(token, macAddressStr.c_str()) != NULL) {
                        entry.entryFound = true;
                        foundEntry = entry;
                        break;
                    }

                    it++;
                }

                if (foundEntry.entryFound) {
                    break;
                }

                token = strtok(NULL, ",");
            }

            if (foundEntry.entryFound) {
                dst->write_line(build_line_str(foundEntry).c_str());
            }

        } while (bytePos <= currentSize);

        // Write the ones not found.
        auto it = tempList.begin();

        while (it != tempList.end()) {
            BleRecentEntry entry = (BleRecentEntry)*it;

            if (!entry.entryFound) {
                dst->write_line(build_line_str(entry).c_str());
            }

            it++;
        }

        // Close files before renaming/deleting.
        src.reset();
        dst.reset();

        // Delete original and overwrite with temp file.
        delete_file(path);
        rename_file(tempFilePath, path);
    }

    tempList.clear();

    return BLE_RX_NO_ERROR;
}

void BLERxView::on_data(BlePacketData* packet) {
    uint64_t uniqueKeyEncoded = copy_mac_address_to_uint64(packet->macAddress);

    // Start of Packet stuffing.
    // Masking off the top 2 bytes to avoid invalid keys.

    uint64_t key = (uniqueKeyEncoded & 0xFFFFFFFFFFFF);

    if (duplicatePackets) {
        key |= ((uint64_t)packet->type) << 48;
    }

    // If found store into tempEntry to modify.
    auto it = find(recent, key);
    if (it != recent.end()) {
        recent.push_front(*it);
        recent.erase(it);
    } else {
        recent.emplace_front(key);
        truncate_entries(recent);
    }

    if (updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type)) {
        if (!searchList.empty()) {
            auto it = searchList.begin();

            while (it != searchList.end()) {
                std::string searchStr = (std::string)*it;

                if (recent.front().dataString.find(searchStr) != std::string::npos) {
                    searchList.erase(it);
                    found_count++;
                    break;
                }

                it++;
            }

            text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
        }

        // Packets were updated, resort the list.
        handle_entries_sort(options_sort.selected_index());
    } else {
        recent.pop_front();
    }

    log_ble_packet(packet);
}

void BLERxView::log_ble_packet(BlePacketData* packet) {
    if ((logger && logging) || serial_logging) {
        str_console = "";
        str_console += pdu_type_to_string((ADV_PDU_TYPE)packet->type);
        str_console += " Len:";
        str_console += to_string_dec_uint(packet->size);
        str_console += " Mac:";
        str_console += to_string_mac_address(packet->macAddress, 6, false);
        str_console += " Data:";

        int i;

        for (i = 0; i < packet->dataLen; i++) {
            str_console += to_string_hex(packet->data[i], 2);
        }
    }

    // Log at End of Packet.
    if (logger && logging) {
        logger->log_raw_data(str_console);
    }

    if (serial_logging) {
        UsbSerialAsyncmsg::asyncmsg(str_console);  // new line handled there, no need here.
    }
}

void BLERxView::on_filter_change(std::string value) {
    // New filter? Reset list from recent entries.
    if (filter != value) {
        filter = value;
        recent.clear();
    }
}

void BLERxView::on_file_changed(const std::filesystem::path& new_file_path) {
    file_path = new_file_path;
    found_count = 0;
    total_count = 0;
    searchList.clear();

    {  // Get the size of the data file.
        File data_file;
        auto error = data_file.open(file_path, true, false);
        if (error) {
            file_error();
            file_path = "";
            return;
        }

        uint64_t bytesRead = 0;
        uint64_t bytePos = 0;
        char currentLine[maxLineLength];

        do {
            memset(currentLine, 0, maxLineLength);

            bytesRead = readUntil(data_file, currentLine, maxLineLength, '\n');

            // Remove return if found.
            if (currentLine[strlen(currentLine)] == '\r') {
                currentLine[strlen(currentLine)] = '\0';
            }

            if (!bytesRead) {
                break;
            }

            searchList.push_back(currentLine);
            total_count++;

            bytePos += bytesRead;

        } while (bytePos <= data_file.size());
    }
}

// called each 1/60th of second, so 6 = 100ms
void BLERxView::on_timer() {
    if (++timer_count == timer_period) {
        timer_count = 0;

        if (auto_channel) {
            field_frequency.set_value(get_freq_by_channel_number(channel_number));
            baseband::set_btlerx(channel_number);

            channel_number = (channel_number < 39) ? channel_number + 1 : 37;
        }
    }
    if (ble_rx_error != BLE_RX_NO_ERROR) {
        if (ble_rx_error == BLE_RX_LIST_FILENAME_EMPTY_ERROR) {
            nav_.display_modal("Error", "List filename is empty !");
        } else if (ble_rx_error == BLE_RX_ENTRY_FILENAME_EMPTY_ERROR) {
            nav_.display_modal("Error", "Entry filename is empty !");
        } else if (ble_rx_error == BLE_RX_LIST_SAVE_ERROR) {
            nav_.display_modal("Error", "Couldn't save list !");
        } else if (ble_rx_error == BLE_RX_ENTRY_SAVE_ERROR) {
            nav_.display_modal("Error", "Couldn't save entry !");
        }
        ble_rx_error = BLE_RX_NO_ERROR;
    }
}

void BLERxView::handle_entries_sort(uint8_t index) {
    switch (index) {
        case 0:
            sortEntriesBy(
                recent, [](const BleRecentEntry& entry) { return entry.uniqueKey & 0xFFFFFFFFFFFF; }, true);
            break;
        case 1:
            sortEntriesBy(
                recent, [](const BleRecentEntry& entry) { return entry.numHits; }, false);
            break;
        case 2:
            sortEntriesBy(
                recent, [](const BleRecentEntry& entry) { return entry.dbValue; }, false);
            break;
        case 3:
            sortEntriesBy(
                recent, [](const BleRecentEntry& entry) { return entry.timestamp; }, false);
            break;
        case 4:
            sortEntriesBy(
                recent, [](const BleRecentEntry& entry) { return entry.nameString; }, true);
            break;
        case 5:
            sortEntriesBy(
                recent, [](const BleRecentEntry& entry) { return entry.informationString; }, true);
            break;
        default:
            break;
    }

    recent_entries_view.set_dirty();
}

bool BLERxView::handle_filter_options(uint8_t index, const BleRecentEntry& entry) {
    const std::string& value = filter;  // no copy
    if (value.empty()) return true;

    switch (index) {
        case 0:  // Data OR Name
            return std::string_view(entry.dataString).find(value) != std::string_view::npos || std::string_view(entry.nameString).find(value) != std::string_view::npos;

        case 1:  // MAC
            return std::string_view(to_string_mac_address(entry.packetData.macAddress, 6, false)).find(value) != std::string_view::npos;

        case 2:  // Name
            return std::string_view(entry.nameString).find(value) != std::string_view::npos;

        case 3:  // Info
            return std::string_view(entry.informationString).find(value) != std::string_view::npos;

        case 4:  // Vendor
            return std::string_view(entry.vendor_name).find(value) != std::string_view::npos;

        case 5:  // Channel (string once, not per call)
            return std::string_view(to_string_dec_int(entry.channelNumber)).find(value) != std::string_view::npos;

        default:
            return true;
    }
}

void BLERxView::set_parent_rect(const Rect new_parent_rect) {
    View::set_parent_rect(new_parent_rect);
    const Rect content_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height - switch_button_height};
    recent_entries_view.set_parent_rect(content_rect);
}

BLERxView::~BLERxView() {
    portapack::async_tx_enabled = async_tx_states_when_entered;
    receiver_model.disable();
    baseband::shutdown();
}

bool BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
    std::string data_string;

    bool success = false;

    // Only parse name for advertisment packets and empty name entries
    if (pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) {
        if (uniqueParsing) {
            success = parse_tracking_beacon_data(packet->data, packet->dataLen, entry.nameString, entry.informationString);
        }

        if (!success && !uniqueParsing) {
            success = parse_beacon_data(packet->data, packet->dataLen, entry.nameString, entry.informationString);
        }
    } else if (pdu_type == ADV_DIRECT_IND || pdu_type == SCAN_REQ) {
        if (!uniqueParsing) {
            ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)packet->data;
            reverse_byte_array(directed_mac_data->A1, 6);
            success = true;
        }
    }

    if (success) {
        int i;

        for (i = 0; i < packet->dataLen; i++) {
            data_string += to_string_hex(packet->data[i], 2);
        }

        entry.dbValue = packet->max_dB - (receiver_model.lna() + receiver_model.vga() + (receiver_model.rf_amp() ? 14 : 0));
        entry.timestamp = to_string_timestamp(rtc_time::now());
        entry.dataString = data_string;

        entry.packetData.type = packet->type;
        entry.packetData.size = packet->size;
        entry.packetData.dataLen = packet->dataLen;

        // Mac Address of sender.
        entry.packetData.macAddress[0] = packet->macAddress[0];
        entry.packetData.macAddress[1] = packet->macAddress[1];
        entry.packetData.macAddress[2] = packet->macAddress[2];
        entry.packetData.macAddress[3] = packet->macAddress[3];
        entry.packetData.macAddress[4] = packet->macAddress[4];
        entry.packetData.macAddress[5] = packet->macAddress[5];

        entry.pduType = pdu_type;
        entry.channelNumber = channel_number;
        entry.numHits++;

        if (entry.vendor_status == MAC_VENDOR_UNKNOWN) {
            entry.vendor_status = lookup_mac_vendor_status(entry.packetData.macAddress, entry.vendor_name);
        }

        // Parse Data Section into buffer to be interpretted later.
        for (int i = 0; i < packet->dataLen; i++) {
            entry.packetData.data[i] = packet->data[i];
        }

        entry.include_name = check_name.value();
    }

    return handle_filter_options(options_filter.selected_index(), entry) && success;
}

bool BLERxView::parse_tracking_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString) {
    uint8_t currentByte, currentLength, currentType = 0;

    for (currentByte = 0; currentByte < length;) {
        currentLength = data[currentByte++];
        currentType = data[currentByte++];

        // Manufacturer Specific Data (0xFF)
        if (currentType == 0xFF && currentLength >= 4) {
            uint16_t companyID = data[currentByte] | (data[currentByte + 1] << 8);

            // Apple AirTag / Find My
            if (companyID == 0x004C && currentLength >= 6) {
                uint8_t appleType = data[currentByte + 2];
                uint8_t appleLen = data[currentByte + 3];
                if (appleType == 0x12 && appleLen == 0x19 && currentLength >= 4 + appleLen) {
                    nameString.assign("Apple AirTag");
                    informationString.assign("Find My");
                    return true;
                } else if (appleType == 0x02 && appleLen == 0x15) {
                    uint16_t major = (data[currentByte + 20] << 8) | data[currentByte + 21];
                    uint16_t minor = (data[currentByte + 22] << 8) | data[currentByte + 23];

                    nameString.assign("iBeacon");
                    informationString.assign(to_string_hex(major) + to_string_hex(minor));
                    return true;
                }
            }
        }
        // Services
        else if ((currentType == 0x02 || currentType == 0x03) && currentLength >= 3) {
            for (int u = 0; u < currentLength - 1; u += 2) {
                uint16_t uuid16 = data[currentByte + u] | (data[currentByte + u + 1] << 8);
                if (uuid16 == 0x1802) {  // Immediate Alert Service = Find Me Profile
                    nameString.assign("FindMe");
                    informationString.assign("IAS");
                    return true;
                }
            }
        }
        // Service Data
        else if (currentType == 0x16 && currentLength >= 3) {
            uint16_t uuid16 = data[currentByte] | (data[currentByte + 1] << 8);

            switch (uuid16) {
                case 0xFD59: {  // Samsung SmartTag - Unregistered
                    nameString.assign("Samsung SmartTag");
                    informationString.assign("Unreg");
                    return true;
                }
                case 0xFD5A: {  // Samsung SmartTag - Registered
                    nameString.assign("Samsung SmartTag");
                    informationString.assign("Reg");
                    return true;
                }
                case 0xFD84: {
                    if (currentByte + 2 < length) {
                        uint8_t model = data[currentByte + 2];
                        switch (model) {
                            case 0x01:
                                informationString.assign("Mate");
                                break;
                            case 0x02:
                                informationString.assign("Pro");
                                break;
                            case 0x03:
                                informationString.assign("Slim");
                                break;
                            case 0x04:
                                informationString.assign("Sticker");
                                break;
                            default:
                                informationString.assign("Unknown");
                                break;
                        }
                    }
                    nameString.assign("Tile");
                    return true;
                }
                case 0xFEAA: {
                    if (currentByte + 2 < length) {
                        uint8_t frameType = data[currentByte + 2];
                        switch (frameType) {
                            case 0x00:
                                informationString.assign("UID");
                                break;
                            case 0x10:
                                informationString.assign("URL");
                                break;
                            case 0x20:
                                informationString.assign("TLM");
                                break;
                            case 0x30:
                                informationString.assign("EID");
                                break;
                            default:
                                informationString.assign("Unknown");
                                break;
                        }
                    }
                    nameString.assign("Eddystone");
                    return true;
                }
            }
        }

        currentByte += (currentLength - 1);
    }

    return false;
}

bool BLERxView::parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString) {
    uint8_t currentByte, currentLength, currentType = 0;
    std::string tempName = "";

    for (currentByte = 0; currentByte < length;) {
        currentLength = data[currentByte++];
        currentType = data[currentByte++];

        // Subtract 1 because type is part of the length.
        for (int i = 0; ((i < currentLength - 1) && (currentByte < length)); i++) {
            // parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
            if (currentType == 0x08 || currentType == 0x09) {
                tempName += (char)data[currentByte];
            }
            currentByte++;
        }

        if (!tempName.empty()) {
            nameString = tempName;
            break;
        }
    }

    informationString = "";

    if (!informationString.empty()) {
        // Option to change title of Hits Column.
        // Setting to default for now.
        columns.set(1, "Hits", 7);
    }

    return true;
}

} /* namespace ui */
